/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Save File to.
 *
 * The Initial Developer of the Original Code is
 * Atte Kemppilä.
 * Portions created by the Initial Developer are Copyright (C) 2007
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 * ***** END LICENSE BLOCK ***** */


function DataSourceTreeView(dataSource)
{
    this.dataSource = dataSource;
    
    var self = this;
    this.dataSourceListener = dataSource.addListener(function() { self.onDataSourceChange(); });
}

DataSourceTreeView.prototype =
{
    get entries()
    {
        var entries = this.dataSource.entries;
        this.entryCount = entries.length;
        return entries;
    },
    
    set entries(entries)
    {
        if (this.setInProgress)
            throw new Error("set already in progress");
        
        try
        {
            this.setInProgress = true;
            
            this.dataSource.entries = entries;
            this.entryCount = entries.length;
        }
        finally
        {
            delete this.setInProgress;
        }
    },
    
    get selectionIterator()
    {
        var selection = this.selection;
        var rangeCount = selection.getRangeCount();
        var start = {};
        var end = {};
        
        for (var i = 0; i < rangeCount; i++)
        {
            selection.getRangeAt(i, start, end);
            for (var k = start.value; k <= end.value; k++)
                yield k;
        }
    },
    
    get selectedIndexes()
    {
        return [index for each (index in this.selectionIterator)];
    },
    
    get selectedIndex()
    {
        // selection.currentIndex is the currently focused index, but it's not neccessarily selected
        if (this.selection.count == 1)
            return this.selectionIterator.next();
        else
            return -1;
    },
    
    get selectedEntries()
    {
        return [this.entries[index] for each (index in this.selectionIterator)];
    },
    
    get selectedEntry()
    {
        var index = this.selectedIndex;
        return ((index != -1) ? this.entries[index] : null);
    },
    
    dispose: function()
    {
        this.dataSource.removeListener(this.dataSourceListener);
        this.dataSource.dispose(true);
    },
    
    onDataSourceChange: function()
    {
        // listen only external changes
        if (this.setInProgress)
            return;
        
        var oldSize = this.entryCount;
        var newSize = this.entries.length;
        
        this.clearSelection();
        this.treeBoxObject.rowCountChanged(oldSize, newSize - oldSize);
        this.treeBoxObject.invalidate();
    },
    
    getCellAt: function(x, y)
    {
        var row = {};
        var col = {};
        var childElement = {};
        this.treeBoxObject.getCellAt(x, y, row, col, childElement);
        return {row: row.value, col: col.value, child: childElement.value};
    },
    
    getColumnType: function(col)
    {
        var index = col.id.lastIndexOf("_");
        return col.id.substr(index + 1);
    },
    
    addEntries: function(index, newEntries, select)
    {
        var boxObject = this.treeBoxObject;
        var selection = this.selection;
        
        var entries = this.entries.slice();
        Array.prototype.splice.apply(entries, [index, 0].concat(newEntries));
        this.entries = entries;
        
        boxObject.rowCountChanged(index, newEntries.length);
        boxObject.ensureRowIsVisible(index);
        if (select)
            selection.rangedSelect(index, index + newEntries.length - 1, false);
        else
            selection.clearSelection();
        selection.currentIndex = index;
    },
    
    setEntry: function(index, entry)
    {
        var boxObject = this.treeBoxObject;
        var selection = this.selection;
        
        var entries = this.entries.slice();
        entries[index] = entry;
        this.entries = entries;
        
        boxObject.invalidateRow(index);
        boxObject.ensureRowIsVisible(index);
        selection.select(index);
    },
    
    removeEntries: function(indexes)
    {
        var boxObject = this.treeBoxObject;
        var selection = this.selection;
        
        var selectEventsSuppressed = selection.selectEventsSuppressed;
        selection.selectEventsSuppressed = true;
        boxObject.beginUpdateBatch();
        
        var entries = this.entries.slice();
        var removed = [];
        var currentIndex = selection.currentIndex;
        indexes = indexes.slice();
        indexes.reverse();
        
        for each (var index in indexes)
        {
            var entry = entries.splice(index, 1)[0];
            removed.push(entry);
            boxObject.rowCountChanged(index, -1);
            
            if (index < currentIndex)
                currentIndex--;
        }
        
        if (entries.length > 0)
        {
            if (currentIndex == entries.length)
                currentIndex--;
                
            boxObject.ensureRowIsVisible(currentIndex);
            selection.select(currentIndex);
        }
        
        this.entries = entries;
        
        boxObject.endUpdateBatch();
        selection.selectEventsSuppressed = selectEventsSuppressed;
        
        // re-reverse removed because indexes were reversed
        removed.reverse();
        return removed;
    },
    
    moveEntriesBy: function(indexes, delta)
    {
        var boxObject = this.treeBoxObject;
        var selection = this.selection;
        
        selection.selectEventsSuppressed = true;
        boxObject.beginUpdateBatch();
        
        var entries = this.entries.slice();
        var startIndex = indexes[0];
        var endIndex = indexes[indexes.length - 1];
        var entryArray = entries.splice(startIndex, indexes.length);
        Array.prototype.splice.apply(entries, [startIndex + delta, 0].concat(entryArray));
        this.entries = entries;
        
        boxObject.invalidateRange(Math.min(startIndex, startIndex + delta), Math.max(endIndex, endIndex + delta));
        selection.rangedSelect(startIndex + delta, endIndex + delta, false);
        boxObject.ensureRowIsVisible((delta < 0) ? Math.max(0, (startIndex + delta - 1)) : Math.min(entries.length - 1, (endIndex + delta + 1)));
        
        boxObject.endUpdateBatch();
        selection.selectEventsSuppressed = false;
    },
    
    moveEntriesTo: function(indexes, targetIndex)
    {
        var boxObject = this.treeBoxObject;
        var selection = this.selection;
        
        var selectEventsSuppressed = selection.selectEventsSuppressed;
        selection.selectEventsSuppressed = true;
        boxObject.beginUpdateBatch();
        
        var entries = this.removeEntries(indexes);
        
        for (var i = indexes.length - 1; i >= 0; i--)
            if (indexes[i] < targetIndex)
                targetIndex--;
        
        this.addEntries(targetIndex, entries, true);
        
        boxObject.endUpdateBatch();
        selection.selectEventsSuppressed = selectEventsSuppressed;
    },
    
    toggleMenu: function(index)
    {
        var entries = this.entries;
        var entry = entries[index];
        if (entry.type != "folder")
            return;
        
        entries = entries.slice();
        entries[index].menu = (! entries[index].menu);
        this.entries = entries;
        
        this.treeBoxObject.invalidateRow(index);
    },
    
    clearSelection: function()
    {
        var selection = this.selection;
        selection.clearSelection();
        selection.currentIndex = -1;
    },
    
    
    // nsITreeView
    
    get rowCount() { return this.entries.length; },
    
    setTree: function(treeBoxObject)
    {
        this.treeBoxObject = treeBoxObject;
    },
    
    getCellText: function(row, col)
    {
        var colType = this.getColumnType(col);
        var entry = this.entries[row];
        
        switch (colType)
        {
            case "name": return this._getNameCellText(entry);
            case "value": return this._getValueCellText(entry);
            case "menu": return;
        }
    },
    
    _getNameCellText: function(entry)
    {
        switch (entry.type)
        {
            case "folder": return entry.label;
            case "item": return entry.label;
            case "recent": return prefwindow.bundle.getString("recent");
            default: return entry.type;
        }
    },
    
    _getValueCellText: function(entry)
    {
        switch (entry.type)
        {
            case "folder": return entry.path;
            default: return "";
        }
    },
    
    isSeparator: function(row)
    {
        return (this.entries[row].type == "separator");
    },
    
    isEditable: function(row, col)
    {
        return ((this.entries[row].type == "folder") && (this.getColumnType(col) == "menu"));
    },
    
    getCellValue: function(row, col)
    {
        var entry = this.entries[row];
        return (("menu" in entry) && entry.menu);
    },
    
    setCellValue: function(row, col, value)
    {
        var entries = this.entries.slice();
        entries[row].menu = (value == "true");
        this.entries = entries;
        
        this.selection.select(row);
    },
    
    getCellProperties: function(row, col, props)
    {
        var name = "type-" + this.entries[row].type;
        props.AppendElement(atomSvc.getAtom(name));
    },
    
    canDrop: function(row, orientation)
    {
        return (this.dnd && this.dnd.canDrop(row, orientation));
    },
    
    drop: function(row, orientation)
    {
        this.dnd.drop(row, orientation);
    },
    
    isContainer: function(row) { return false; },
    isSorted: function() { return false; },
    getLevel: function(row) { return 0; },
    getImageSrc: function(row, col) { return null; },
    getColumnProperties: function(colid, col, props) {},
    getRowProperties: function(row, props) {},
    cycleHeader: function(col) {},
    getParentIndex: function(row) { return -1; }
}
